
lexer grammar ScIom;

tokens {
    ARGS;
    EXPR;
    STMTS;
}

@header {
package org.sciom.parser;

import java.io.Reader;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileReader;
import java.io.PrintStream;
import java.io.StringReader;

import java.util.ArrayList;
import java.util.List;
}

@members {
    private Token preToken = null;
    private Token peekToken = null;

    public Token topToken() {
        if(peekToken == null) {
            peekToken = nextToken();
        }
        return peekToken;
    }

    public Token nextToken() {
        if(peekToken != null) {
            Token ret = peekToken;
            peekToken = null;
            return ret;
        }
        if(preToken != null) {
            Token ret = preToken;
            preToken = null;
            return ret;
        }
        Token t = super.nextToken();
        if(preToken != null) {
            Token ret = preToken;
            preToken = t;
            return ret;
        }
        return t;
    }

    public static ScIomLexer getLexerFor(String input) throws Exception {
        return getLexerFor(new StringReader(input));
    }

    public static ScIomLexer getLexerFor(Reader input) throws Exception {
        return new ScIomLexer(new ANTLRReaderStream(input));
    }

    public static boolean isValidMessageName(Token token) throws Exception {
        switch(token.getType()) {
            case Identifier:
            case MonoQuote:
            case TriQuote:
            case Number:
            case HexNumber:
                return true;
        }
        return false;
    }

    public static List<Token> getNormalizedTokens(Reader reader) throws Exception {
        return doNormalizedLexing(getLexerFor(reader));
    }

    private static List<Token> doNormalizedLexing(ScIomLexer lexer) throws Exception {
        List<Token> tokens = new ArrayList<Token>();
        Token t;
        Token last=null;
        while((t = lexer.nextToken()).getType() != EOF) {
            // All of these special cases are things that the IO Lexer emits
            // They will probably be omitted in the parser, since they aren't really necessary
            if(((t.getType() == OpenParen && t.getText().equals("(")))
                     && (last == null || (last.getType() != Identifier && last.getType() != MonoQuote))) {
                tokens.add(new CommonToken(Identifier, ""));
            } else if(t.getType() != Terminator && last == null && t.getLine() != 1) {
                tokens.add(new CommonToken(Terminator, ";"));
            }
            tokens.add(t);
            last = t;
        } 
        if(last != null && last.getType() != Terminator && last.getType() != CloseParen && last.getType() != MonoQuote && last.getType() != TriQuote && last.getType() != Identifier) {
            tokens.add(new CommonToken(Terminator, ";"));
        }
        return tokens;
    }

    public static void main(final String[] args) throws Exception {
        Reader reader;
        if(args.length > 0) {
            reader = new BufferedReader(new FileReader(args[0]));
        } else {
            reader = new InputStreamReader(System.in);
        }
        List<Token> tokens = getNormalizedTokens(reader);
        for(Token t : tokens) {
            System.out.println("{" + tokenToName(t.getType()) + "} " + t.getText());
        }
    }

    public final static String tokenToName(int token) {
        switch(token) {
        case OpenParen: return "OpenParen";
        case Comma: return "Comma";
        case CloseParen: return "CloseParen";
        case TriQuote: return "TriQuote";
        case MonoQuote: return "MonoQuote";
        case HexNumber: return "HexNumber";
        case Number: return "Number";
        case Identifier: return "Identifier";
        case Terminator: return "Terminator";
        case Comment: return "Comment";
        case ARGS: return "ARGS";
        case EXPR: return "EXPR";
        case STMTS: return "STMTS";
        case EOF: return "EOF";
        default: return "UNKNOWN TOKEN(" + token + ")";
        }
    }

    private int last = -1;
    private int xlast = 0;

    private boolean h=false;

    private void handleTerminator(int tp) {
        if(last == Terminator || last == OpenParen || last == Comma) {
            skip();
        }
        last = tp;
        setText(";");    
    }
}

OpenParen : ('(' | '[' {preToken=new CommonToken(Identifier, "squareBrackets");} | '{' {preToken=new CommonToken(Identifier, "curlyBrackets");}) {last=$type;};
Comma : ',' {last=$type;};
CloseParen : (')' | ']' | '}') {last=$type;};

TriQuote : ('"""' ( options {greedy=false;} : .* ) '"""') {last=$type;};
MonoQuote : ('"' ( ('\\' ('"'|'\\')) | '\\'? ~('"'|'\\'))* '"') {last=$type;};

HexNumber : '0' ('x' | 'X') (Digit | HexLetter)* {last=$type;};
Number : (Digits ('.' Digits)? (('e'|'E') '-'? Digits)? 
    | '.' Digits) {last=$type;}
    ;

Identifier : 
        ((( Letter | '_') (Letter | Digit | '_' | '.' | ':')*) { 
                // Ugly, ugly, ugly solution. Of course, the grammar for this case is ugly
                if(getText().charAt(getText().length()-1)==':' && input.LA(1) == '=') {
                    setText(getText().substring(0,getText().length()-1));
                    xlast = -2;
                }
            }
        | '=' {if(last == -2) setText(":=");}
        | (':'? ':' '=')
        | ('@'? '@')
        | '\''
        | ('.'? '.')
        | '?'
        | ('*'? '*')
        | ('+'? '+')
        | ('-' ('-' | '>')?)
        | '/'
        | '%'
        | ('<'? '<' '='?)
        | ('>'? '>' '='?)
        | (('=' | '!' | '+' | '-' | '*' | '/' | '%' | '&' | '^' | '|') '=')
        | ('&'? '&')
        | '^'
        | ('|'? '|')
        | ('<' '-' '>'?))
        {last=$type; if(xlast != 0) {last = xlast; xlast=0;}};

Terminator : ((';' | '\u000a' | '\u000d')+) {handleTerminator($type);} ;

Whitespace : Separator {skip();};

Comment :
        (('/*' ( options {greedy=false;} : .* ) '*/')
    |   '//' ( ~NewLine )* ( NewLine {$type=Terminator; handleTerminator($type);h=true;} )?
    |   '#' ( ~NewLine )* ( NewLine {$type=Terminator; handleTerminator($type);h=true;} )?)
        {if(!h){skip();}h=false;}
    ;

fragment
Letter : 'a' .. 'z' | 'A' .. 'Z' ;

fragment
Digit : '0'..'9' ;

fragment
Digits : Digit+ ;

fragment
HexLetter : 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' ;

fragment
Separator : (' ' | '\u000c' | '\u0009' | '\u000b' | '\\' '\u000a' )* ;

fragment
NewLine : ('\u000a' | '\u000d') ;
